#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <endian.h>
#include <modbus/modbus.h>
#include <unistd.h>
#include "redis.h"
#include "gpio.h"
#include "channel.h"
#include "z_logger.h"
#include "modbus_acquire.h"

static int _gpio_fd = -1;

static void _modbus_set_rts(modbus_t *ctx, int on) {
    if (_gpio_fd <= 0) return;

    gpio_set_value(_gpio_fd, on);
}

static void _endian_convert(thing_model_t *thing_model, uint16_t *payload, int nb) {
    int i;

    if (thing_model->swap_byte) {
        swap_byte((uint8_t *) payload, nb * 2);
    }

    if (nb >= 2) {
#if __BYTE_ORDER == __LITTLE_ENDIAN
        if (!thing_model->swap_word) {
            swap_word(payload, nb);
        }
#else
        if(thing_model->swap_word) {
            swap_word(payload, nb);
        }
#endif
    }

    if (nb >= 3) {
#if __FLOAT_WORD_ORDER == __LITTLE_ENDIAN
        if (!thing_model->swap_dword) {
            swap_dword((uint32_t *) payload, nb / 2);
        }
#else
        if (thing_model->swap_dword) {
            swap_dword((uint32_t *) payload, nb / 2);
        }
#endif
    }
}

static double _value_get_simple(value_t *value) {
    switch (value->clazz) {
        case vc_bit:
            return value->bval;
        case vc_float:
            return value->fval;
        case vc_double:
            return value->dval;
        default:
            return value->ival;
    }
}

void value_transform(device_t *device, thing_model_t *model, value_t *value) {
    switch (model->value_type) {
        case vt_normalized: {
            double x = _value_get_simple(value);

            int decimal = model->decimal;
            if (model->mul_u && model->mul_i) {
                decimal = device->p_decimal;
            } else if (model->mul_u && !model->mul_i) {
                decimal = device->u_decimal;
            } else if (!model->mul_u && model->mul_i) {
                decimal = device->i_decimal;
            }

            value->value = x / pow(10, decimal);
            break;
        }
        case vt_scaled: {
            double x = _value_get_simple(value);

            double bb = 1.0f;
            if (model->mul_u) {
                bb *= (device->u0 * device->ubb);
            }
            if (model->mul_i) {
                bb *= (device->i0 * device->ibb);
            }

            double scale = model->scale;
            if (0.0f == scale) {
                scale = 1.0f;
            }

            value->value = x * bb / scale + model->adjust;
            break;
        }
        default:
            value->value = _value_get_simple(value);
            break;
    }
}

void modbus_decode_registers(device_t *device, thing_model_t *thing_model, const uint16_t *payload, value_t *value) {
    uint16_t val[4] = {};

    int nb = get_data_len(thing_model->data_type);
    memcpy(val, payload, sizeof(uint16_t) * nb);
    _endian_convert(thing_model, val, nb);

    switch (thing_model->data_type) {
        case dt_bit:
            value_set_bit(value, 0 != val[0] ? 1 : 0);
            break;
        case dt_byte:
            value_set_short(value, val[0] & 0xff);
            break;
        case dt_short:
            value_set_short(value, val[0]);
            break;
        case dt_neg_short: {
            uint16_t v = val[0];
            uint16_t f = (1 << 15);
            value_set_short(value, (v & f) ? -(v & ~f) : (v & ~f));
            break;
        }
        case dt_word:
            value_set_int(value, (uint16_t) val[0]);
            break;
        case dt_integer:
            value_set_int(value, *(int32_t *) &val);
            break;
        case dt_neg_int: {
            uint32_t v = *(uint32_t *) &val;
            uint32_t f = (1 << 31);
            value_set_int(value, (v & f) ? -(v & ~f) : (v & ~f));
            break;
        }
        case dt_dword:
            value_set_long(value, *(uint32_t *) &val);
            break;
        case dt_mid:
            val[3] = val[2] & 0x8000 ? 0xffff : 0x0000;
            value_set_long(value, *(int64_t *) &val);
            break;
        case dt_neg_mid: {
            uint64_t v = *(uint64_t *) &val;
            uint64_t f = (1 << 15) * 0x100000000L;
            value_set_long(value, (v & f) ? -(v & ~f) : (v & ~f));
            break;
        }
        case dt_tword:
            val[3] = 0x0000;
            value_set_long(value, *(uint64_t *) &val);
            break;
        case dt_long:
            value_set_long(value, *(int64_t *) &val);
            break;
        case dt_neg_long: {
            uint64_t v = *(uint64_t *) &val;
            uint64_t f = (1 << 31) * 0x100000000L;
            value_set_long(value, (v & f) ? -(v & ~f) : (v & ~f));
            break;
        }
        case dt_qword:
            value_set_long(value, *(uint64_t *) &val);
            break;
        case dt_float:
            value_set_float(value, *(float *) &val);
            break;
        case dt_double:
            value_set_double(value, *(double *) &val);
            break;
        default:
            break;
    }
}

int modbus_collect_cols(channel_t *channel, device_t *device,
                        segment_e segment, vector_t *tag_list, int offset, int nb) {
    int rc = -1;
    int i, j, k;
    uint8_t payload[MODBUS_MAX_READ_BITS];

    modbus_set_slave(channel->modbus, device->slave_id);
    if (seg_do == segment) {
        rc = modbus_read_bits(channel->modbus, offset, nb, payload);
    } else if (seg_di == segment) {
        rc = modbus_read_input_bits(channel->modbus, offset, nb, payload);
    }
    update_stats(device, channel, rc);

    if (rc < 0) {
        log_warn("modbus_read_*_bits=%d", rc);
        return rc;
    }

    for (i = 0; i < vector_size(tag_list); ++i) {
        thing_model_t *tag = vector_get(tag_list, i);

        for (j = 0, k = offset; j < nb; ++j, ++k) {
            if (segment == tag->segment && k == tag->offset) {
                value_t value;

                time(&value.timestamp);

                value_set_bit(&value, 0 != payload[j] ? 1 : 0);
                redis_set_value(device, tag, &value);
                redis_pub_value(channel, device, tag, &value);
            }
        }
    }

    return rc;
}

int modbus_collect_registers(channel_t *channel, device_t *device,
                             segment_e segment, vector_t *tag_list, int offset, int nb) {
    int rc = 0;
    int i, j, k;
    uint16_t payload[MODBUS_MAX_READ_REGISTERS];

    modbus_set_slave(channel->modbus, device->slave_id);
    if (seg_ao == segment) {
        rc = modbus_read_registers(channel->modbus, offset, nb, payload);
    } else if (seg_ai == segment) {
        rc = modbus_read_input_registers(channel->modbus, offset, nb, payload);
    }
    update_stats(device, channel, rc);

    if (rc < 0) {
        log_warn("modbus_read_*_registers=%d", rc);
        return rc;
    }

    for (i = 0; i < vector_size(tag_list); ++i) {
        thing_model_t *tag = vector_get(tag_list, i);

        for (j = 0, k = offset; j < nb; ++j, ++k) {
            if (segment == tag->segment && k == tag->offset) {
                value_t value;
                value_set_null(&value);

                if (0 == tag->time_tag) {
                    time(&value.timestamp);
                }

                modbus_decode_registers(device, tag, &payload[j], &value);
                value_transform(device, tag, &value);
                redis_set_value(device, tag, &value);
                redis_pub_value(channel, device, tag, &value);
            }
        }
    }

    return rc;
}

int modbus_collect(channel_t *channel, device_t *device,
                   segment_e segment, vector_t *tag_list, int offset, int nb) {
    log_debug("modbus_collect channel=%d, slave=%d, seg=%d, off=0x%04x, nb=%d",
              device->channel, device->slave_id, segment, offset, nb);

    if (seg_do == segment || seg_di == segment) {
        return modbus_collect_cols(channel, device, segment, tag_list, offset, nb);
    } else if (seg_ao == segment || seg_ai == segment) {
        return modbus_collect_registers(channel, device, segment, tag_list, offset, nb);
    }

    return -1;
}

static int lua_parse(channel_t *channel, uint16_t slave, char *lua, int *value) {
    int rc = -1;
    char seg[4] = {}, hl = '\0';
    uint16_t offset;

    if (sscanf(lua, "${%c%c:%hu:%c}", &seg[0], &seg[1], &offset, &hl) >= 3) {
        if ('a' == seg[0] || 'A' == seg[0]) {
            uint16_t val = 0;

            if ('o' == seg[1] || 'O' == seg[1]) {
                modbus_set_slave(channel->modbus, slave);
                rc = modbus_read_registers(channel->modbus, offset, 1, &val);
            } else if ('i' == seg[1] || 'I' == seg[1]) {
                modbus_set_slave(channel->modbus, slave);
                rc = modbus_read_input_registers(channel->modbus, offset, 1, &val);
            }

            if (rc > 0) {
                if ('\0' == hl) {
                    *value = (int16_t) val;
                } else if ('h' == hl || 'H' == hl) {
                    *value = (int8_t) ((val & 0xff00) >> 8);
                } else if ('l' == hl || 'L' == hl) {
                    *value = (int8_t) (val & 0x00ff);
                }
            }
        } else if ('d' == seg[0] || 'D' == seg[0]) {
            uint8_t val = 0;

            if ('o' == seg[1] || 'O' == seg[1]) {
                modbus_set_slave(channel->modbus, slave);
                rc = modbus_read_bits(channel->modbus, offset, 1, &val);
            } else if ('i' == seg[1] || 'I' == seg[1]) {
                modbus_set_slave(channel->modbus, slave);
                rc = modbus_read_input_bits(channel->modbus, offset, 1, &val);
            }

            if (rc > 0) {
                *value = (0 != val);
            }
        }
    } else if (lua[0] >= '0' && lua[0] <= '9') {
        *value = atoi(lua);
        rc = (0 != *value) ? 0 : -1;
    }

    return rc;
}

#define LUA_PARSE_CHECK(x)  if ('\0' != product->x[0]) { \
        int rc = lua_parse(channel, device->slave_id, product->x, &device->x); \
        if(rc < 0) return -1; \
}

int modbus_init(channel_t *channel, device_t *device) {
    //已经初始化，直接返回
    if (device->_stat.initialized) {
        return 0;
    }

    product_t *product = device->_product;

    LUA_PARSE_CHECK(u0)
    LUA_PARSE_CHECK(i0)
    LUA_PARSE_CHECK(ubb)
    LUA_PARSE_CHECK(ibb)
    LUA_PARSE_CHECK(u_decimal)
    LUA_PARSE_CHECK(i_decimal)
    LUA_PARSE_CHECK(p_decimal)

    device->_stat.initialized = true;

    return 0;
}

int channel_open_modbus(channel_t *channel) {
    if (channel_is_serial(channel->type) && cp_modbus_rtu == channel->protocol) {
        if (channel->serial.gpio > 0) {
            _gpio_fd = gpio_open(channel->serial.gpio, "direction");
        }
        serial_params_t *params = &channel->serial;
        char parity = (1 == params->parity) ? 'O' : 2 == params->parity ? 'E' : 'N';
        channel->modbus = modbus_new_rtu(channel->serial.device,
                                         params->baud,
                                         parity,
                                         params->data_bits,
                                         params->stop_bits);
        modbus_rtu_set_serial_mode(channel->modbus, MODBUS_RTU_RS485);
        modbus_rtu_set_rts(channel->modbus, MODBUS_RTU_RTS_UP);
        modbus_rtu_set_custom_rts(channel->modbus, _modbus_set_rts);
    } else if (channel->type == ct_tcpip && channel->protocol == cp_modbus_tcp) {
        int port = channel->tcpip.port;
        if (0 == port) {
            port = MODBUS_TCP_DEFAULT_PORT;
        }
        channel->modbus = modbus_new_tcp(channel->tcpip.addr, port);
    } else {
        channel->modbus = NULL;
        return -1;
    }

    int timeout = channel->timeout;
    if (0 != timeout) {
        modbus_set_response_timeout(channel->modbus, timeout / 1000, timeout % 1000 * 1000);
    }
    modbus_set_error_recovery(channel->modbus, MODBUS_ERROR_RECOVERY_LINK | MODBUS_ERROR_RECOVERY_PROTOCOL);
    modbus_set_debug(channel->modbus, channel->_debug);

    channel->fd = modbus_connect(channel->modbus);
    if (-1 == channel->fd) {
        if (channel_is_serial(channel->type)) {
            log_warn("can't connect to serial %s", channel->serial.device);
        } else if (ct_tcpip == channel->type) {
            log_warn("can't connect to ipaddr %s:%d", channel->tcpip.addr, channel->tcpip.port);
        } else {
            log_warn("can't connect to modbus channel %d", channel->super.id);
        }
    }
    return channel->fd;
}

void channel_close_modbus(channel_t *channel) {
    if (NULL != channel->modbus) {
        close(_gpio_fd);
        gpio_unexport(channel->serial.gpio);

        modbus_close(channel->modbus);
        modbus_free(channel->modbus);

        channel->fd = -1;
        channel->modbus = NULL;
    }
}
